/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.service.support;


import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import net.sf.cglib.beans.BeanMap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.vacoor.nothing.common.util.Resources;
import org.vacoor.nothing.web.entity.Variable;
import org.vacoor.nothing.web.service.ConfigurationManager;
import org.vacoor.nothing.web.util.SettingsUtils;

import java.io.File;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author vacoor
 */
//@Service
public class ConfigurationManagerImpl extends CrudServiceImpl<Variable, String> implements ConfigurationManager, ApplicationContextAware, InitializingBean {
    private static final File CONFIG_FILE = Resources.getResourceAsFile("config.json"); //new File("/Users/vacoor/IdeaProjects/nothing/nothing-security/src/main/resources/config.json");
    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    protected final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    protected final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    protected Map<String, Variable> cache;

    public ConfigurationManagerImpl() {
        // super(CONFIG_FILE, "name");
    }

    // TreeSet ??
    public List<Variable> findAll() {
        System.out.println("find all");
        List<Variable> vars = super.findAll();

        writeLock.lock();
        try {
            cache = Maps.newHashMap(Maps.uniqueIndex(vars, new Function<Variable, String>() {
                @Override
                public String apply(Variable input) {
                    return input.getName();
                }
            }));
        } finally {
            writeLock.unlock();
        }

        return vars;
    }

    protected List<Variable> doFindAll() {
        return super.findAll();
    }

    public Variable find(String key) {
        System.out.println("find");
        readLock.lock();
        try {
            return cache.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public void update(Variable var) {
        writeLock.lock();
        try {
            Variable orig = cache.get(var.getName());
            BeanMap.create(orig).putAll(BeanMap.create(var));
        } finally {
            writeLock.unlock();
        }
    }

    public void save(Variable t) {
        writeLock.lock();
        try {
            super.save(t);
            cache.put(t.getName(), t);
        } finally {
            writeLock.unlock();
        }
    }


    @Override
    public void update(Map<String, String> values) {
        for (Map.Entry<String, String> kv : values.entrySet()) {
            String varname = kv.getKey();
            String varvalue = kv.getValue();
            Variable variable = find(varname);
            if (null != variable) {
                variable.setValue(varvalue);
                update(variable);
            }
        }
    }

    @Override
    public Properties getConfiguration() {
        ConfigurationManager configurationManager = SettingsUtils.getConfigurationManager();
        if (true) {
            return doCreateConfig();
        }

        Map<String, String> o = Maps.transformValues(cache, new Function<Variable, String>() {
            @Override
            public String apply(Variable input) {
                String v = input.getValue();
                return null != v ? v : "";
            }
        });
        Properties props = new Properties(System.getProperties());
        props.putAll(o);
        return props;
    }

    protected Properties doCreateConfig() {
        ConfigurationManager configurationManager = SettingsUtils.getConfigurationManager();
        return new DelegatingProperties(configurationManager, System.getProperties());
    }

    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

//    @PostConstruct
    public void afterPropertiesSet() throws Exception {
        ConfigurationManager configurationManager = applicationContext.getBean(ConfigurationManager.class);
        configurationManager.findAll();
    }

    /* *************************************
     *
     * *************************************/

    private static class DelegatingProperties extends Properties {
        private transient ConfigurationManager configurationManager;

        public DelegatingProperties(ConfigurationManager configurationManager) {
            this(configurationManager, null);
        }

        public DelegatingProperties(ConfigurationManager configurationManager, Properties defaults) {
            super(defaults);
            this.configurationManager = configurationManager;
        }

        @Override
        public synchronized Enumeration<Object> keys() {
            return Iterators.asEnumeration(keySet().iterator());
        }

        @Override
        public Set<Object> keySet() {
            final List<Variable> all = configurationManager.findAll();
            return Collections.synchronizedSet(new AbstractSet<Object>() {
                @Override
                public Iterator<Object> iterator() {
                    return Iterators.transform(all.iterator(), new Function<Variable, Object>() {
                        @Override
                        public Object apply(Variable input) {
                            return input.getName();
                        }
                    });
                }

                @Override
                public int size() {
                    return all.size();
                }
            });
            // return super.keySet();
        }

        @Override
        public Set<Map.Entry<Object, Object>> entrySet() {
            final List<Variable> all = configurationManager.findAll();
            return new AbstractSet<Map.Entry<Object, Object>>() {
                @Override
                public Iterator<Map.Entry<Object, Object>> iterator() {
                    return Iterators.transform(all.iterator(), new Function<Variable, Map.Entry<Object, Object>>() {
                        @Override
                        public Map.Entry<Object, Object> apply(Variable input) {
                            String v = input.getValue();
                            v = null != v ? v : "";
                            return Maps.<Object, Object>immutableEntry(input.getName(), v);
                        }
                    });
                }

                @Override
                public int size() {
                    return all.size();
                }
            };
        }

        @Override
        public synchronized Object put(Object key, Object value) {
            // return super.put(key, value);
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized void putAll(Map<?, ?> t) {
            // super.putAll(t);
            throw new UnsupportedOperationException();
        }

        @Override
        public String getProperty(String key) {
            Object oval = get(key);
            String sval = (oval instanceof String) ? (String) oval : null;
            return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
        }

        @Override
        public synchronized Object get(Object key) {
            Object ret = null;
            if (key instanceof String) {
                Variable var = configurationManager.find((String) key);
                ret = null != var ? var.getValue() : null;
            }
            ret = null != ret ? ret : super.get(key);
            return ret;
        }

        @Override
        public synchronized Object remove(Object key) {
            // return super.remove(key);
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized void clear() {
            // super.clear();
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized int size() {
            return configurationManager.findAll().size();
        }
    }
}